package grpc

import (
	"context"
	"errors"
	"fmt"
	"github.com/sirupsen/logrus"
	"gitlab.yctc.tech/zhiting/disk-manager.git/internal/config"
	"gitlab.yctc.tech/zhiting/disk-manager.git/pkg/disk"
	"gitlab.yctc.tech/zhiting/disk-manager.git/pkg/lvm"
	"gitlab.yctc.tech/zhiting/disk-manager.git/pkg/proto"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"
	"syscall"
	"time"
)

var (
	ErrCannotEditSystemDisk = status.Error(codes.InvalidArgument, "can not edit __system__ disk")
	ErrCannotUseSystemDisk  = status.Error(codes.InvalidArgument, "can not use __system__ disk")
	ErrEmptyName            = status.Error(codes.InvalidArgument, "vg/pv/lv name can not empty")
)

const (
	systemDiskName = "__system__"
	// 默认权限：rwxrwxrwx
	defaultPerm    = os.ModePerm
)

// DiskManagerServer gRPC 服务端
type DiskManagerServer struct {
	opts       *config.Options
	volumePath string
	skipPVS    map[string]bool
	// 某些接口只能串行操作，需要做互斥
	mu sync.Mutex
	proto.UnimplementedDiskManagerServer
}

// NewDiskManagerServer 实例化 DiskManagerServer
func NewDiskManagerServer(opts *config.Options) *DiskManagerServer {
	p, err := filepath.Abs(strings.TrimRight(opts.VolumePath, "/"))
	if err != nil {
		log.Fatalln(err)
	}
	skipPVS := make(map[string]bool)
	for _, pv := range opts.SkipPVS {
		skipPVS[pv] = true
	}
	return &DiskManagerServer{
		volumePath: p,
		opts:       opts,
		skipPVS:    skipPVS,
	}
}

// InitEnv 初始化目录，并且挂载对应的盘
func (s *DiskManagerServer) InitEnv() (err error) {
	_ = os.MkdirAll(s.volumePath, defaultPerm)
	f, err := os.Stat(s.volumePath)
	if os.IsNotExist(err) {
		return errors.New("volume path not exist")
	}
	if !f.IsDir() {
		return errors.New("volume path is not dir")
	}
	out, errt := exec.Command("vgimport", "-a").Output()
	if errt != nil {
		logrus.Warnf("vgimport err %v, %v", errt, out)
	}
	out, errt = exec.Command("pvchange", "-a", "-x", "y").Output()
	if errt != nil {
		logrus.Warnf("pvchange err %v, %v", errt, out)
	}
	// 系统盘
	_ = os.MkdirAll(fmt.Sprintf("%s%c%s%c%s", s.volumePath, os.PathSeparator, systemDiskName,
		os.PathSeparator, systemDiskName), defaultPerm)

	ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
	defer cancel()
	lvs, terr := lvm.LVList(ctx)
	if terr != nil {
		return
	}
	// 挂载分区
	for _, lv := range lvs {
		if len(lv.VGName) == 0 {
			continue
		}
		if lv.Name == systemDiskName || lv.VGName == systemDiskName {
			continue
		}
		if terr := os.MkdirAll(s.lvTargetPath(lv), defaultPerm); terr != nil {
			continue
		}
		target := s.lvTargetPath(lv)
		isMount, isTarget := disk.IsPartitionMountOn(lv.Path, target)
		if !isMount {
			if err = syscall.Mount(lv.Path, target, "ext4", 0, ""); err != nil {
				logrus.Warnf("mount %v to %v error %v", lv.Path, target, err)
				return err
			}
		} else if !isTarget {
			return fmt.Errorf("disk %v is not mount on %v", lv.Path, target)
		}
	}
	return nil
}

func (s *DiskManagerServer) PhysicalVolumeList(ctx context.Context, _ *proto.Empty) (*proto.PhysicalVolumeListResp, error) {
	pvs, err := lvm.PVList(ctx)
	if err != nil {
		return nil, wrapLvmError(err)
	}
	rpvs := make([]*proto.PhysicalVolume, 0)
	rpvs = append(rpvs, s.systemPV())
	for _, pv := range pvs {
		if s.pvIsSkip(pv.Name) {
			continue
		}
		rpvs = append(rpvs, wrapPV(pv))
	}
	resp := proto.PhysicalVolumeListResp{PVS: rpvs}
	return &resp, nil
}

func (s *DiskManagerServer) VolumeGroupList(ctx context.Context, _ *proto.Empty) (*proto.VolumeGroupListResp, error) {
	vgs, err := lvm.VGList(ctx)

	if err != lvm.ErrEmpty && err != nil {
		return nil, wrapLvmError(err)
	}
	// 只读，可能为空
	pvs, _ := lvm.PVList(ctx)
	lvs, _ := lvm.LVList(ctx)

	rvgs := make([]*proto.VolumeGroup, 0, len(vgs)+1)

	size, _, freeSize := disk.GetPartitionSizes(s.opts.SystemLV)
	sysVG := &proto.VolumeGroup{
		Name:     systemDiskName,
		Exported: false,
		Size:     size,
		FreeSize: freeSize,
		UUID:     "",
		LVS: []*proto.LogicalVolume{
			s.systemLV(),
		},
		PVS: []*proto.PhysicalVolume{
			s.systemPV(),
		},
	}
	rvgs = append(rvgs, sysVG)
	for _, vg := range vgs {
		rvgs = append(rvgs, wrapVG(vg, lvs, pvs))
	}

	resp := proto.VolumeGroupListResp{VGS: rvgs}
	return &resp, nil
}

func (s *DiskManagerServer) VolumeGroupCreate(ctx context.Context, req *proto.VolumeGroupCreateOrExtendReq) (*proto.VolumeGroupResp, error) {
	// 串行操作
	s.mu.Lock()
	defer s.mu.Unlock()

	if len(req.VGName) == 0 || len(req.PVName) == 0 {
		return nil, ErrEmptyName
	}
	if req.VGName == systemDiskName || req.PVName == systemDiskName {
		return nil, ErrCannotUseSystemDisk
	}
	if s.pvIsSkip(req.PVName) {
		return nil, status.Error(codes.InvalidArgument, "can not use this pv")
	}
	// 可能已经挂载
	disk.TryUnmountPartition(req.PVName)
	vg, err := lvm.VGCreate(ctx, req.VGName, req.PVName)
	if err != nil {
		return nil, wrapLvmError(err)
	}
	_ = os.MkdirAll(fmt.Sprintf("%s%c%s", s.volumePath, os.PathSeparator,
		vg.Name), defaultPerm)
	return wrapVolumeGroupResp(ctx, vg), nil
}

func (s *DiskManagerServer) VolumeGroupExtend(ctx context.Context, req *proto.VolumeGroupCreateOrExtendReq) (*proto.VolumeGroupResp, error) {
	// 串行操作
	s.mu.Lock()
	defer s.mu.Unlock()

	if req.VGName == systemDiskName || req.PVName == systemDiskName {
		return nil, ErrCannotEditSystemDisk
	}
	// 可能已经挂载
	disk.TryUnmountPartition(req.PVName)
	vg, err := lvm.VGExtend(ctx, req.VGName, req.PVName)
	if err != nil {
		return nil, wrapLvmError(err)
	}
	return wrapVolumeGroupResp(ctx, vg), nil
}

func (s *DiskManagerServer) VolumeGroupRename(ctx context.Context, req *proto.VolumeGroupRenameReq) (*proto.VolumeGroupResp, error) {
	// 串行操作
	s.mu.Lock()
	defer s.mu.Unlock()

	if len(req.NewName) == 0 {
		return nil, ErrEmptyName
	}
	if req.OldName == systemDiskName || req.NewName == systemDiskName {
		return nil, ErrCannotEditSystemDisk
	}
	vg, err := lvm.VGRename(ctx, req.OldName, req.NewName)
	if err != nil {
		return nil, wrapLvmError(err)
	}
	return wrapVolumeGroupResp(ctx, vg), nil
}

func (s *DiskManagerServer) VolumeGroupRemove(ctx context.Context, req *proto.VolumeGroupRemoveReq) (*proto.Empty, error) {
	// 串行操作
	s.mu.Lock()
	defer s.mu.Unlock()

	if len(req.VGName) == 0 {
		return nil, ErrEmptyName
	}
	if req.VGName == systemDiskName{
		return nil, ErrCannotEditSystemDisk
	}

	if err := lvm.VGRemove(ctx, req.VGName, s.volumePath); err != nil {
		return nil, wrapLvmError(err)
	}
	return &proto.Empty{}, nil
}

// LogicalVolumeCreate 创建分区，并且初始化文件系统
func (s *DiskManagerServer) LogicalVolumeCreate(ctx context.Context, req *proto.LogicalVolumeCreateReq) (*proto.LogicalVolumeResp, error) {
	// 串行操作
	s.mu.Lock()
	defer s.mu.Unlock()

	if len(req.LVName) == 0 {
		return nil, ErrEmptyName
	}
	if req.VGName == systemDiskName || req.LVName == systemDiskName {
		return nil, ErrCannotUseSystemDisk
	}
	lv, err := lvm.LVCreate(ctx, req.VGName, req.LVName, req.SizeM)
	if err != nil {
		return nil, wrapLvmError(err)
	}
	err = disk.MakeExt4(ctx, lv.Path)
	if err != nil {
		// 回滚
		_ = lvm.LVRemove(ctx, req.VGName, req.LVName)
		return nil, err
	}
	target := s.lvTargetPath(lv)
	_ = os.MkdirAll(target, defaultPerm)
	if err = syscall.Mount(lv.Path, target, "ext4", 0, ""); err != nil {
		_ = os.RemoveAll(target)
		_ = lvm.LVRemove(ctx, req.VGName, req.LVName)
		return nil, err
	}
	resp := proto.LogicalVolumeResp{Data: wrapLV(lv)}
	return &resp, nil
}

func (s *DiskManagerServer) LogicalVolumeRename(ctx context.Context, req *proto.LogicalVolumeRenameReq) (*proto.LogicalVolumeResp, error) {
	// 串行操作
	s.mu.Lock()
	defer s.mu.Unlock()

	if len(req.NewLVName) == 0 {
		return nil, ErrEmptyName
	}
	if req.VGName == systemDiskName || req.LVName == systemDiskName {
		return nil, ErrCannotEditSystemDisk
	}
	lvs, err := lvm.LVList(ctx)
	if err != nil {
		return nil, wrapLvmError(err)
	}
	oldTarget := fmt.Sprintf("%s%c%s%c%s", s.volumePath, os.PathSeparator,
		req.VGName, os.PathSeparator, req.LVName)
	if _, ok := lvs[fmt.Sprintf("%s/%s", req.VGName, req.LVName)]; ok {
		_ = syscall.Unmount(oldTarget, 0)
	}
	lv, err := lvm.LVRename(ctx, req.VGName, req.LVName, req.NewLVName)
	if err != nil {
		return nil, wrapLvmError(err)
	}
	// 已经重命名
	target := s.lvTargetPath(lv)
	_ = os.MkdirAll(target, defaultPerm)
	if err = syscall.Mount(lv.Path, target, "ext4", 0, ""); err != nil {
		// TODO 是否有可能失败？如何回滚？
		return nil, err
	}
	_ = os.RemoveAll(oldTarget)
	resp := proto.LogicalVolumeResp{Data: wrapLV(lv)}
	return &resp, nil
}

func (s *DiskManagerServer) LogicalVolumeExtend(ctx context.Context, req *proto.LogicalVolumeExtendReq) (*proto.LogicalVolumeResp, error) {
	// 串行操作
	s.mu.Lock()
	defer s.mu.Unlock()

	if req.VGName == systemDiskName || req.LVName == systemDiskName {
		return nil, ErrCannotEditSystemDisk
	}
	lv, err := lvm.LVExtend(ctx, req.VGName, req.LVName, req.NewSizeM)
	if err != nil {
		return nil, wrapLvmError(err)
	}
	// 扩展文件系统，扩展不需要unmount
	if err = disk.PartitionExtend(lv.Path); err != nil {
		// 错误不返回
		logrus.Warnf("extend fs error: %v", err)
	}
	resp := proto.LogicalVolumeResp{Data: wrapLV(lv)}
	return &resp, nil
}

func (s *DiskManagerServer) LogicalVolumeRemove(ctx context.Context, req *proto.LogicalVolumeRemoveReq) (*proto.Empty, error) {
	// 串行操作
	s.mu.Lock()
	defer s.mu.Unlock()

	if req.VGName == systemDiskName || req.LVName == systemDiskName {
		return nil, ErrCannotEditSystemDisk
	}
	lvs, err := lvm.LVList(ctx)
	if err != nil {
		return nil, wrapLvmError(err)
	}
	// try umount
	lv, ok := lvs[fmt.Sprintf("%s/%s", req.VGName, req.LVName)]
	if ok {
		target := s.lvTargetPath(lv)
		_ = syscall.Unmount(target, 0)
	}
	if err = lvm.LVRemove(ctx, req.VGName, req.LVName); err != nil {
		return nil, wrapLvmError(err)
	}
	if ok {
		target := s.lvTargetPath(lv)
		_ = os.RemoveAll(target)
	}
	return &proto.Empty{}, nil
}

func (s *DiskManagerServer) lvTargetPath(lv *lvm.LogicalVolume) string {
	return fmt.Sprintf("%s%c%s%c%s", s.volumePath, os.PathSeparator,
		lv.VGName, os.PathSeparator, lv.Name)
}

func (s *DiskManagerServer) pvIsSkip(pv string) bool {
	_, ok := s.skipPVS[pv]
	return ok
}

func (s *DiskManagerServer) systemPV() *proto.PhysicalVolume {
	size, _, _ := disk.GetPartitionSizes(s.opts.SystemLV)
	return &proto.PhysicalVolume{
		Name:     systemDiskName,
		VGName:   systemDiskName,
		Size:     size,
		UUID:     "",
		MetaData: "",
	}
}

func (s *DiskManagerServer) systemLV() *proto.LogicalVolume {
	size, _, freeSize := disk.GetPartitionSizes(s.opts.SystemLV)
	return &proto.LogicalVolume{
		Name:     systemDiskName,
		VGName:   systemDiskName,
		Size:     size,
		FreeSize: freeSize,
		Path:     s.opts.SystemLV,
		UUID:     "",
	}
}
